﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Globalization;
using System.Web.Routing;
using System.Reflection;

namespace System.Web.Mvc.Ajax
{
    /// <summary>
    /// 自定义AjaxForm类
    /// </summary>
    internal class AjaxForm : IDisposable
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="helper">AjaxHelper对象</param>
        /// <param name="formAction">form提交的url</param>
        /// <param name="options">AjaxHelper.AjaxOptions对象</param>
        /// <param name="additionalAttributes">form的附加属性，属性值中可以使用{formId}代表使用的form对象的id值</param>
        /// <param name="writer">存储HTML代码的StringBuilder对象</param>
        /// <param name="useHtmlEncode">对生成的HTML代码是否使用进行HTML编码</param>
        public AjaxForm(AjaxHelper helper, string formAction, AjaxOptions options, IDictionary<string, object> additionalAttributes,
            StringBuilder writer, bool useHtmlEncode)
        {
            this.Helper = helper;
            this.FormAction = formAction;
            this.Options = options;
            this._writer = writer;
            this._useHtmlEncode = useHtmlEncode;
            this._originalFormContext = this.Helper.ViewContext.FormContext;
            this.Helper.ViewContext.FormContext = new FormContext();
            this._additionalAttributes = additionalAttributes;
            BeginForm();
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="helper">AjaxHelper对象</param>
        /// <param name="formAction">form提交的url</param>
        /// <param name="options">AjaxHelper.AjaxOptions对象</param>
        /// <param name="additionalAttributes">form的附加属性，属性值中可以使用{formId}代表使用的form对象的id值</param>
        /// <param name="writer">存储HTML代码的StringBuilder对象</param>
        public AjaxForm(AjaxHelper helper, string formAction, AjaxOptions options, IDictionary<string, object> additionalAttributes, StringBuilder writer)
            : this(helper, formAction, options, additionalAttributes, writer, true)
        {

        }

        /// <summary>
        /// 获取相关的AjaxHelper对象
        /// </summary>
        public AjaxHelper Helper { get; private set; }
        /// <summary>
        /// 获取form提交的url
        /// </summary>
        public string FormAction { get; private set; }
        /// <summary>
        /// 获取AjaxHelper对象的AjaxOptions对象
        /// </summary>
        public AjaxOptions Options { get; private set; }
        /// <summary>
        /// 获取生成的form的id
        /// </summary>
        public string FormId { get; private set; }

        #region private members

        private bool _useHtmlEncode = true;
        private bool _isEnded;
        private StringBuilder _writer;
        private const string FormOnClickValue = "Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));";
        private const string FormOnSubmitFormat = "Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), {0});";
        private readonly FormContext _originalFormContext;
        private IDictionary<string, object> _additionalAttributes;
        
        #endregion

        /// <summary>
        /// 生成form的HTML代码
        /// </summary>
        private void BeginForm()
        {
            TagBuilder builder = new TagBuilder("form");

            builder.MergeAttribute("action", this.FormAction);
            builder.MergeAttribute("method", "post");

            if (this.Helper.ViewContext.UnobtrusiveJavaScriptEnabled)
            {
                IDictionary<string, string> formatedAttributes = FormatAttributes(this.Options.ToUnobtrusiveHtmlAttributes());
                builder.MergeAttributes(formatedAttributes);
            }
            else
            {
                builder.MergeAttribute("onclick", FormOnClickValue);
                builder.MergeAttribute("onsubmit", GenerateOptionsAjaxScript(FormOnSubmitFormat));
            }
            
            if (this.Helper.ViewContext.ClientValidationEnabled)
            {
                // forms must have an ID for client validation
                //builder.GenerateId(this.Helper.ViewContext.FormIdGenerator());
                this.FormId = DefaultFormIdGenerator();
                builder.GenerateId(this.FormId);
            }

            if (_additionalAttributes != null && _additionalAttributes.Count > 0)
            {
                IDictionary<string, string> formatedAttributes = FormatAttributes(_additionalAttributes);

                builder.MergeAttributes(formatedAttributes);
            }

            if (this.Helper.ViewContext.ClientValidationEnabled)
            {
                this.Helper.ViewContext.FormContext.FormId = builder.Attributes["id"];
            }
            string tagHtml = GetTagHtml(builder.ToString(TagRenderMode.StartTag));
            _writer.AppendLine(tagHtml);
        }

        private IDictionary<string, string> FormatAttributes(IDictionary<string, object> attributes)
        {
            Dictionary<string, string> _replaceAttributes = new Dictionary<string, string>();
            foreach (var item in attributes)
            {
                _replaceAttributes[item.Key] = attributes[item.Key].ToString().Replace("{formId}", this.FormId);
            }
            return _replaceAttributes;
        }        

        private string GetTagHtml(string builderToString)
        {
            string tagHtml = builderToString;
            if (!_useHtmlEncode)
            {
                tagHtml = HttpUtility.HtmlDecode(tagHtml);
            }
            return tagHtml;
        }

        private static readonly object _lastFormNumKey = new object();
        private string DefaultFormIdGenerator()
        {
            PropertyInfo[] props = this.Helper.ViewContext.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Instance);
            PropertyInfo prop = props.SingleOrDefault(p => p.Name == "FormIdGenerator");
            if (prop != null)
            {
                Func<string> func = (Func<string>)prop.GetValue(this.Helper.ViewContext, null);
                return func();
            }
            int randomValue = DateTime.Now.Second + DateTime.Now.Millisecond;
            return randomValue.ToString();
        }

        /// <summary>
        /// 添加form的子对象
        /// </summary>
        /// <param name="tagName">标签</param>
        /// <param name="attributes">属性字典</param>
        public void AppendChild(string tagName, Dictionary<string, string> attributes)
        {
            TagBuilder builder = new TagBuilder(tagName);
            if (attributes != null && attributes.Count > 0)
            {
                builder.MergeAttributes<string, string>(attributes, true);
            }
            string tagHtml = GetTagHtml(builder.ToString(TagRenderMode.SelfClosing));

            _writer.AppendLine(tagHtml);
        }

        /// <summary>
        /// 在form中添加html片段
        /// </summary>
        /// <param name="html">html代码片段</param>
        public void AppendHtml(string html)
        {
            _writer.AppendLine(html);
        }

        /// <summary>
        /// <![CDATA[在form中生成成对标签的开始代码，例如:<a href="#" title="link">]]>
        /// </summary>
        /// <param name="tagName">标签</param>
        /// <param name="attributes">属性字典</param>
        public void BeginChild(string tagName, Dictionary<string, string> attributes)
        {
            TagBuilder builder = new TagBuilder(tagName);
            if (attributes != null && attributes.Count > 0)
            {
                builder.MergeAttributes<string, string>(attributes, true);
            }
            string tagHtml = GetTagHtml(builder.ToString(TagRenderMode.StartTag));
            _writer.AppendLine(tagHtml);
        }

        /// <summary>
        /// <![CDATA[在form中生成成对标签的关闭代码与BeginChild方法一起使用，例如：</a>]]>
        /// </summary>
        /// <param name="tagName">标签</param>
        public void EndChild(string tagName)
        {
            TagBuilder builder = new TagBuilder(tagName);
            string tagHtml = GetTagHtml(builder.ToString(TagRenderMode.EndTag));
            _writer.AppendLine(tagHtml);
        }

        private string GenerateOptionsAjaxScript(string scriptFormat)
        {
            //string optionsString = this.Options.ToJavascriptString();
            string optionsString = GetOptionJavaScriptString(this.Options);
            return String.Format(CultureInfo.InvariantCulture, scriptFormat, optionsString);
        }

        private string GetOptionJavaScriptString(AjaxOptions options)
        {
            MethodInfo[] methods = options.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);
            MethodInfo toJavascriptString = methods.SingleOrDefault(m => m.Name == "ToJavascriptString");
            if (toJavascriptString != null)
            {
                return toJavascriptString.Invoke(options, null).ToString();
            }
            return DefaultToJavascriptString(options);
        }

        private string DefaultToJavascriptString(AjaxOptions options)
        {
            StringBuilder builder = new StringBuilder("{");
            string insertionModeString = options.InsertionMode.ToString();
            switch (options.InsertionMode)
            {
                case System.Web.Mvc.Ajax.InsertionMode.Replace:
                    insertionModeString = "Sys.Mvc.InsertionMode.replace";
                    break;
                case System.Web.Mvc.Ajax.InsertionMode.InsertBefore:
                    insertionModeString = "Sys.Mvc.InsertionMode.insertBefore";
                    break;
                case System.Web.Mvc.Ajax.InsertionMode.InsertAfter:
                    insertionModeString = "Sys.Mvc.InsertionMode.insertAfter";
                    break;
            }
            builder.Append(string.Format(CultureInfo.InvariantCulture, " insertionMode: {0},", new object[] { insertionModeString }));
            builder.Append(PropertyStringIfSpecified("confirm", options.Confirm));
            builder.Append(PropertyStringIfSpecified("httpMethod", options.HttpMethod));
            builder.Append(PropertyStringIfSpecified("loadingElementId", options.LoadingElementId));
            builder.Append(PropertyStringIfSpecified("updateTargetId", options.UpdateTargetId));
            builder.Append(PropertyStringIfSpecified("url", options.Url));
            builder.Append(EventStringIfSpecified("onBegin", options.OnBegin));
            builder.Append(EventStringIfSpecified("onComplete", options.OnComplete));
            builder.Append(EventStringIfSpecified("onFailure", options.OnFailure));
            builder.Append(EventStringIfSpecified("onSuccess", options.OnSuccess));
            builder.Length--;
            builder.Append(" }");
            return builder.ToString();
        }

        private string PropertyStringIfSpecified(string propertyName, string propertyValue)
        {
            if (!string.IsNullOrEmpty(propertyValue))
            {
                string str = propertyValue.Replace("'", @"\'");
                return string.Format(CultureInfo.InvariantCulture, " {0}: '{1}',", new object[] { propertyName, str });
            }
            return string.Empty;
        }

        private string EventStringIfSpecified(string propertyName, string handler)
        {
            if (!string.IsNullOrEmpty(handler))
            {
                return string.Format(CultureInfo.InvariantCulture, " {0}: Function.createDelegate(this, {1}),", new object[] { propertyName, handler.ToString() });
            }
            return string.Empty;
        }

        /// <summary>
        /// 释放资源并关闭form
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (!_isEnded)
            {
                _isEnded = true;
                this._writer.AppendLine("</form>");
                this.Helper.ViewContext.OutputClientValidation();
                this.Helper.ViewContext.FormContext = this._originalFormContext;
            }
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
        }

        /// <summary>
        /// 结束form并输出
        /// </summary>
        public void EndAndFlush()
        {
            Dispose(true);
        }
    }
}
